//
//    Copyright (c) Microsoft Corporation.  All rights reserved.
//
//    inf2rtf.cpp : Defines the entry point for the application.
//

// Includes
#include "stdafx.h"

// Constants
const int LINE_LENGTH = 20000;

// Section names in the INF
#define OC_NAME_HEADER			_T("[Optional Components]")
#define DEFAULT_INSTALL_HEADER	_T("DefaultInstall")
#define CONFIGURATION_HEADER	_T("Configuration")
#define COPYFILES				_T("CopyFiles")
#define ADD_REG					_T("AddReg")
#define DEL_REG					_T("DelReg")
#define DESTINATION_DIRS		_T("[DestinationDirs]")
#define SOURCEDISKSFILES		_T("[SourceDisksFiles]")
#define STRINGS_HEADER			_T("Strings")
#define WINSE_REGISTRY_ADD		_T("ProductInstall.GlobalRegistryChanges.Install")
#define WINSE_ADDFILES_1		_T("[ProductInstall.ReplaceFilesIfExist]")
#define WINSE_ADDFILES_2		_T("[ProductInstall.CopyFilesAlways]")
#define WINSE_ADDFILES_3		_T("[ProductInstall.DontDelayUntilReboot]")
#define UNINSTALL_REG_SECTION	_T("Uninst.Reg")

#define FILE_ADD_HEADER			_T("The following list represents the changed files:\n<br><br>")
#define REG_ADD_HEADER			_T("The following list represents the registry additions:\n<br><br>")
#define REG_REMOVE_HEADER		_T("The following list represents the registry deletions:\n<br><br>")

#define GENERIC_TABLE_HEADER	_T("<table frame=border border=2 cellPadding=5>\n<tr bgcolor=\"silver\">")
#define GENERIC_TABLE_FOOTER	_T("</table>\n<br><br>")

#define FILE_TABLE_HEADER		_T("<td><center><b>Repository Source</b></center></td>\n<td><center><b>Target Destination</b></center></td>\n</tr>")
#define REG_TABLE_HEADER		_T("<td><center><b>Key Name</b></center></td>\n<td><center><b>Value Name</b></center></td>\n<td><center><b>Type</b></center></td>\n<td><center><b>Value</b></center></td>\n</tr>")

#define HTML_FOOTER				_T("</p>\n</font>\n</body>\n</html>")

// Function prototypes
BOOL AddHtmlHeaderToOutputFile(TCHAR*);
BOOL AddLineToLogFile(TCHAR*,TCHAR*);
TCHAR* AddTempPathToFileName(TCHAR*);
BOOL BuildDestinationDirectory(TCHAR*,TCHAR*);
BOOL GetNextLine(FILE*, TCHAR*);
TCHAR* GetNextCommaDelimitedLineItem(TCHAR**);
BOOL GetValue(HKEY,TCHAR*,TCHAR*,DWORD,LPBYTE,DWORD,BOOL bUseRootHiveIf64Bit=TRUE);
BOOL GetValueEx(HKEY,TCHAR*,TCHAR*,DWORD,LPBYTE,DWORD);
BOOL IsCurrentProcessWOW64();
BOOL MergeToMasterLogFile(TCHAR*, TCHAR*);
BOOL ParseDefaultInstallINF(TCHAR*,TCHAR*);
BOOL ParseINF(TCHAR*);
BOOL ParseInfCopyFilesSection(TCHAR*,TCHAR*,TCHAR*);
BOOL ParseInfFileGroup(TCHAR*,TCHAR*,TCHAR*);
BOOL ParseInfRegistryGroup(TCHAR*,TCHAR*);
BOOL ParseInfRegistrySection(TCHAR*,TCHAR*);
BOOL ParseInfRegValue(TCHAR*,TCHAR*);
BOOL ParseWinSEINF(TCHAR*);
BOOL RemoveTrailingSpaces(TCHAR*);
BOOL SearchFileForItem(TCHAR*,TCHAR*);
void ShowUsageInfo();
TCHAR* SubstituteForTokensOld(TCHAR*);
TCHAR* SubstituteForTokens(TCHAR*,TCHAR*);
BOOL ConvertINFToUnicode(TCHAR*);
LPSTR AllocSzFromWsz(LPCWSTR);
LPWSTR AllocWszFromSz(LPCSTR);

// Global variables
BOOL g_bInfParsingSucceeded = TRUE;
HINSTANCE g_hInstance = NULL;
TCHAR *g_pszRtfFilename = NULL;
TCHAR *g_pszErrorLogFilename = NULL;

/******************************************************************
Function Name:  WinMain
Author:  aaronste
Description:
Inputs:
Results:
******************************************************************/
int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
    LPTSTR szCmdLine = NULL;
	TCHAR *pszInfFilename=NULL;
	TCHAR *pszTmpInfFile=NULL;
	TCHAR szLogFilenameBuffer[MAX_PATH*3];
	TCHAR szLogString[MAX_PATH*3];
	BOOL  bSilentFlag = FALSE;
	TCHAR dbuffer[9];
	TCHAR tbuffer[9];
	OPENFILENAME ofn;
	TCHAR szFileName[MAX_PATH] = {0};

	g_hInstance = hInstance;

    szCmdLine = GetCommandLine();
    if( NULL == szCmdLine )
    {
        return 0;
    }

	// Parse out command line parameters
	int j=0;
	for(unsigned int i=0; i<_tcslen(szCmdLine); i++)
	{
		// The /i flag gives full path to an INF file (mandatory)
		if(szCmdLine[i]==_T('/') && (szCmdLine[i+1]==_T('i') || szCmdLine[i+1]==_T('I')) )
		{
			pszInfFilename = new TCHAR[MAX_PATH*3];
			i = i+2;

			// Strip off white space
			while(szCmdLine[i]==_T(' '))
				i++;
			j=0;

			while(szCmdLine[i]!=_T('\0') && szCmdLine[i]!=_T('/'))
			{
				// Skip quotation marks in file names
				if (szCmdLine[i] != _T('"'))
				{
					pszInfFilename[j] = szCmdLine[i];
					j++;
				}
				i++;
			}

			if (szCmdLine[i] == _T('/')) i--;

			pszInfFilename[j]=_T('\0');
			RemoveTrailingSpaces(pszInfFilename);
		}
		// The /q flag will cause inf2rtf to not display any UI (optional)
		if(szCmdLine[i]==_T('/') && (szCmdLine[i+1]==_T('q') || szCmdLine[i+1]==_T('Q')) )
		{
			bSilentFlag = TRUE;
			i = i+2;
		}
		// The /o flag gives a path to the output log file (optional)
		if(szCmdLine[i]==_T('/') && (szCmdLine[i+1]==_T('o') || szCmdLine[i+1]==_T('O')) )
		{
			g_pszRtfFilename = new TCHAR[MAX_PATH];
			i = i+2;

			// Strip off white space
			while(szCmdLine[i]==_T(' '))
				i++;
			j=0;

			while(szCmdLine[i]!=_T('\0') && szCmdLine[i]!=_T('/'))
			{
				// Skip quotation marks in file names
				if (szCmdLine[i] != _T('"'))
				{
					g_pszRtfFilename[j] = szCmdLine[i];
					j++;
				}
				i++;
			}

			if (szCmdLine[i] == _T('/')) i--;

			g_pszRtfFilename[j]=_T('\0');
			RemoveTrailingSpaces(g_pszRtfFilename);
		}
		// The /e flag gives a path to the error log file (optional)
		if(szCmdLine[i]==_T('/') && (szCmdLine[i+1]==_T('e') || szCmdLine[i+1]==_T('E')) )
		{
			g_pszErrorLogFilename = new TCHAR[MAX_PATH];
			i = i+2;

			// Strip off white space
			while(szCmdLine[i]==_T(' '))
				i++;
			j=0;

			while(szCmdLine[i]!=_T('\0') && szCmdLine[i]!=_T('/'))
			{
				// Skip quotation marks in file names
				if (szCmdLine[i] != _T('"'))
				{
					g_pszErrorLogFilename[j] = szCmdLine[i];
					j++;
				}
				i++;
			}

			if (szCmdLine[i] == _T('/')) i--;

			g_pszErrorLogFilename[j]=_T('\0');
			RemoveTrailingSpaces(g_pszErrorLogFilename);
		}
		if(szCmdLine[i]==_T('/') && szCmdLine[i+1]==_T('?'))
		{
			ShowUsageInfo();
			return 0;
		}
	}

	// Check to see if the user passed in an INF filename
	if (!pszInfFilename)
	{
		// First show a browse for file dialog
		// If the user chooses a password.ini, then show the dialog
		ZeroMemory(&ofn, sizeof(ofn));
		
		ofn.lStructSize = sizeof(ofn);
		ofn.hwndOwner = NULL;
		ofn.lpstrFilter = _T("Windows Installation Files (*.inf)\0*.inf\0");
		ofn.lpstrFile = szFileName;
		ofn.nMaxFile = MAX_PATH;
		ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_PATHMUSTEXIST;
		ofn.lpstrDefExt = _T("inf");

		if(GetOpenFileName(&ofn))
		{
			pszInfFilename = new TCHAR[MAX_PATH*3];
			_tcscpy(pszInfFilename,szFileName);
		}
		else
		{
			return 0;
		}
	}

	// If the output filename is NULL, set it to a randomly generated name
	// in the temp directory
	if (!g_pszErrorLogFilename)
	{
		// Use date/time to create a unique output logfile name
		_tstrdate(dbuffer);
		_tstrtime(tbuffer);
		_stprintf_s(szLogFilenameBuffer, MAX_PATH*3, _T("%s_%s_%s"), dbuffer, tbuffer, _T("inf2rtf.txt"));

		// Remove invalid characters
		for (unsigned int k=0; k<_tcslen(szLogFilenameBuffer); k++)
		{
			if (szLogFilenameBuffer[k] == _T('/'))
				szLogFilenameBuffer[k] = _T('-');
			if (szLogFilenameBuffer[k] == _T(':') && k > 2)
				szLogFilenameBuffer[k] = _T('.');
		}
		
		// Create the output log file in the temp directory
		g_pszErrorLogFilename = AddTempPathToFileName(szLogFilenameBuffer);

		if (!g_pszErrorLogFilename)
			return -2;

	}

	// Delete the error log file if it already exists
	DeleteFile(g_pszErrorLogFilename);

	// Create a new filename in the temp dir to copy the INF file to
	// Need to have write access to the file for PrivateProfile functions
	pszTmpInfFile = AddTempPathToFileName(_T("temp_inf.txt"));
	if (!pszTmpInfFile)
		return -3;

	// Try to copy the INF to the temp directory before starting
	if (!CopyFile(pszInfFilename,pszTmpInfFile,FALSE))
	{
		g_bInfParsingSucceeded = FALSE;

		// Log the failure
		_stprintf_s(szLogString,MAX_PATH*3,_T("***ERROR*** Could not find INF file %s"),pszInfFilename);
		AddLineToLogFile(g_pszErrorLogFilename, szLogString);
	}
	else
	{
		// If we get here, we can go ahead and parse the INF
		// Change the attributes on the file in case it is read-only
		SetFileAttributes(pszTmpInfFile, FILE_ATTRIBUTE_NORMAL);

		// Check to see if the file is ANSI or UNICODE and convert
		// to ANSI if necessary
		ConvertINFToUnicode(pszTmpInfFile);

		// Parse the INF
		g_bInfParsingSucceeded = ParseINF(pszTmpInfFile);
	}

	// Delete temp copy of INF file
	DeleteFile(pszTmpInfFile);

	// If the silent flag was not passed in, show the
	// output file in notepad
	if (FALSE == bSilentFlag)
	{
		// Set the filename to display based on whether
		// or not the INF parsing was successful
		if (TRUE == g_bInfParsingSucceeded)
		{
			// Open the output file
			ShellExecute(NULL, _T("open"), g_pszRtfFilename, NULL, NULL, SW_SHOWNORMAL);
		}
		else
		{
			_stprintf_s(szLogString,MAX_PATH*3,_T("\nLook at %s for any partial parsing that was done before any errors occurred"),g_pszRtfFilename);
			AddLineToLogFile(g_pszErrorLogFilename, szLogString);

			// Open the error log file
			ShellExecute(NULL, _T("open"), g_pszErrorLogFilename, NULL, NULL, SW_SHOWNORMAL);

			// Open the output file
			ShellExecute(NULL, _T("open"), g_pszRtfFilename, NULL, NULL, SW_SHOWNORMAL);
		}
	}

	// Clean up
	if (pszTmpInfFile) delete [] pszTmpInfFile;
	if (pszInfFilename) delete [] pszInfFilename;
	if (g_pszRtfFilename) delete [] g_pszRtfFilename;
	if (g_pszErrorLogFilename) delete [] g_pszErrorLogFilename;

	if (g_bInfParsingSucceeded)
		return 0;
	else
		return 100;
}


/******************************************************************
Function Name:  AddHtmlHeaderToOutputFile
Author:  aaronste
Description: Adds header info to the output RTF file
Inputs: pszKBArticleNumber - KB article number
Results: TRUE if successful; FALSE otherwise
******************************************************************/
BOOL AddHtmlHeaderToOutputFile(TCHAR *pszKBArticleNumber)
{
	TCHAR szLogString[MAX_PATH];

	// Check input parameter
	if (NULL == pszKBArticleNumber)
		return FALSE;

	// Build the header string
	_stprintf_s(szLogString, MAX_PATH, _T("<html>\n<head>\n<title>QFE %s Additional Information</title>\n</head>\n<body>\n<font face=Arial>\n<p style=\"font-size: 22pt; font-face: bold;\">\nQFE %s Additional Information\n</p>\n<br>\n<p style=\"font-size: 12pt;\">"), pszKBArticleNumber, pszKBArticleNumber);
	AddLineToLogFile(g_pszRtfFilename, szLogString);

	return TRUE;
}


/******************************************************************
Function Name:  AddLineToLogFile
Author:  aaronste
Description: Adds a line to the master log file
Inputs: pszFilename - name of file to add line to
		pszNewLine - line to add to the file
Results: TRUE if successful; FALSE otherwise
******************************************************************/
BOOL AddLineToLogFile(TCHAR *pszFilename, TCHAR *pszNewLine)
{
	FILE* pfLogFile;

	// Check input parameters
	if ( (!pszFilename) || (!pszNewLine) )
		return FALSE;

	// Open the log file for append
	pfLogFile = _tfopen(pszFilename,_T("a"));
	if (!pfLogFile)
		return FALSE;

	// Print the line to the file
	_ftprintf(pfLogFile, _T("%s\n"), pszNewLine);

	// Close the file
	if (pfLogFile) fclose(pfLogFile);

	return TRUE;
}


/******************************************************************
Function Name: AddTempPathToFileName
Author:  dannyton
Description:  Takes a short filename and builds a long filename
			  that includes the user's temp directory
Inputs:  fileName: The short filename
Returns: The full path to the file or NULL if error
Note: The caller must deallocate memory returned by this function.
******************************************************************/
TCHAR* AddTempPathToFileName(TCHAR* fileName)
{
	TCHAR* szTmpPath = NULL;
	DWORD tmpPathSize = GetTempPath(0, szTmpPath);	// Get the required size

	if(tmpPathSize > 0)	// No error
	{
		tmpPathSize = tmpPathSize + _tcslen(fileName);	// No need to have one more for NULL character
		szTmpPath = new TCHAR[tmpPathSize];
		
		if(!GetTempPath(tmpPathSize, szTmpPath))	// Get the temp path
			return NULL;	// Error

		_tcscat(szTmpPath, fileName);	// Concatenate the filename to the end of the path
	}
	
	return szTmpPath;	// Null if error
}


/******************************************************************
Function Name:  ParseInfCopyFilesSection
Author:  aaronste
Description: Parses the list of section names under CopyFiles in
			 the INF file, and tries to verify the files for
			 each of the sections
Inputs:	pszListOfFileGroups - list of groups of files in the INF
		pszInfFilename - full path to INF file being parsed
		pszKBArticleNumber - KB article number for this INF (Q######)
Results: TRUE if successful, FALSE otherwise
******************************************************************/
BOOL ParseInfCopyFilesSection(TCHAR *pszListOfFileGroups, TCHAR *pszInfFilename, TCHAR *pszKBArticleNumber)
{
	TCHAR *pszFileGroupHeader = NULL;
	BOOL bRetValue = TRUE;

	// If the input parameters are invalid, bail out
	if ( (!pszListOfFileGroups) || (!pszInfFilename) || (!pszKBArticleNumber) )
		return FALSE;

	// The pszListOfFileGroups is a comma-delimited list of sections
	// Parse the string to get each section
	while (pszListOfFileGroups)
	{
		pszFileGroupHeader = GetNextCommaDelimitedLineItem(&pszListOfFileGroups);
		if (pszFileGroupHeader)
		{
			if (!ParseInfFileGroup(pszFileGroupHeader, pszInfFilename, pszKBArticleNumber))
				bRetValue = FALSE;
		}
	}

	return bRetValue;
}


/******************************************************************
Function Name:  ParseInfFileGroup
Author:  aaronste
Description: Reads each file in a section and adds it to the RTF file
Inputs:	pszFileGroupName - name of a group of files in the INF
		pszInfFilename - full path to INF file being parsed
		pszKBArticleNumber - KB article number for this INF (Q######)
Results: TRUE if successful, FALSE otherwise
******************************************************************/
BOOL ParseInfFileGroup(TCHAR *pszFileGroupName, TCHAR *pszInfFilename, TCHAR *pszKBArticleNumber)
{
	FILE *pfInfFile = NULL;
	TCHAR *pszBeginDestDir = NULL;
	TCHAR *pszEndDestDir = NULL;
	TCHAR *pszShortFilename = NULL;
	TCHAR *c;
	TCHAR szDestinationDir[MAX_PATH*3] = {0};
	TCHAR szBracketedFileGroupName[MAX_PATH];
	TCHAR szInfFileLine[LINE_LENGTH];
	TCHAR szLogString[MAX_PATH];
	BOOL bBuildDestDirRetVal = FALSE;
	BOOL bFoundFilesInSection = FALSE;

	// If the input parameters are invalid, bail out
	if ( (NULL == pszFileGroupName) || (NULL == pszInfFilename) || (NULL == pszKBArticleNumber) )
		return FALSE;

	// Open the INF file for reading
	pfInfFile = _tfopen(pszInfFilename, _T("rb"));
	if (!pfInfFile)
		return FALSE;

	// Find the [DestinationDirs] section header in the INF
	while (GetNextLine(pfInfFile, szInfFileLine))
	{
		if (_tcsstr(szInfFileLine,DESTINATION_DIRS))
			break;
	}

	// Now search the [DestinationDirs] section for the file group
	// to get destination directory information
	while (GetNextLine(pfInfFile, szInfFileLine) && (_tcschr(szInfFileLine,'[') != szInfFileLine) )
	{
		// Skip blank lines
		if (0 == _tcscmp(szInfFileLine,_T("")))
			continue;

		// Check to see if the current line has the name of the file group
		// that we're looking for
		if (_tcsstr(szInfFileLine, pszFileGroupName))
		{
			// If we get here, we have the directory information for the current
			// file group, so we need to parse it and build the destination dir
			bBuildDestDirRetVal = BuildDestinationDirectory(szInfFileLine, szDestinationDir);
			break;
		}

	}
	
	// Close the INF file
	if (pfInfFile) fclose(pfInfFile);

	// Reopen the INF file for reading
	pfInfFile = _tfopen(pszInfFilename, _T("rb"));
	if (!pfInfFile)
		return FALSE;

	// Put brackets around the section name, it is passed in without them
	_tcscpy(szBracketedFileGroupName,_T("["));
	_tcscat(szBracketedFileGroupName,pszFileGroupName);
	_tcscat(szBracketedFileGroupName,_T("]"));

	// Find the file group section with the list of files
	while (GetNextLine(pfInfFile, szInfFileLine))
	{
		if (_tcsstr(szInfFileLine,szBracketedFileGroupName))
			break;
	}

	// If BuildDestinationDirectory returned true but the szDestinationDir value
	// is blank - this is because we are intentionally skipping files in DLLCache
	// because they do not exist on XP Embedded, so bail out here in that case
	if (0 == _tcscmp(szDestinationDir, _T("")))
	{
		if (TRUE == bBuildDestDirRetVal)
			return TRUE;
	}

	// Now search the file group section and check to see if each file
	// exists in the specified destination directory on the machine
	// The end of the section will be marked by a new section header or EOF
	while (GetNextLine(pfInfFile, szInfFileLine) && (_tcschr(szInfFileLine,'[') != szInfFileLine) )
	{
		// Skip blank lines
		if (0 == _tcscmp(szInfFileLine,_T("")))
			continue;

		// Get the first part of the file copy line, it is a comma-delimited line
		// where the first item is the destination filename
		if (c = _tcschr(szInfFileLine, _T(',')))
		{
			*c = _T('\0');
		}
		
		// Trim blank characters at the front of the line
		c = szInfFileLine;
		while (c[0] == _T(' '))
			c++;

		_tcscpy(szInfFileLine, c);

		// Skip files named *.cat because catalog files are not installed on
		// XP Embedded because there is no SFP
		if (_tcsstr(szInfFileLine, _T(".cat\0")))
			continue;

		// Skip files with IE_UNINST_EXE in the name - hardcoded hack for IE QFEs
		if (_tcsstr(szInfFileLine, _T("%IE_UNINST_EXE%")))
			continue;

		// Write the file information to the HTML file
		_stprintf_s(szLogString, MAX_PATH, _T("<tr style=\"font-size: 10pt;\">\n<td>%s_%s</td>\n<td>%s\\%s</td>\n</tr>"), pszKBArticleNumber, szInfFileLine, szDestinationDir, szInfFileLine);
		AddLineToLogFile(g_pszRtfFilename, szLogString);

		bFoundFilesInSection = TRUE;
	}

	// Close the file
	if (pfInfFile) fclose(pfInfFile);

	// Check to see if we failed to find a destination directory value but the section
	// had no files in it - we don't want to return false in that case
	if ( (TRUE == bFoundFilesInSection) && (0 == _tcscmp(szDestinationDir, _T(""))) )
	{
		// BuildDestinationDirectory may return "true" if it finds the DLLCache
		// directory, so we only want to log an error if that is not the case
		if (!bBuildDestDirRetVal)
		{
			_stprintf_s(szLogString,MAX_PATH,_T("***ERROR*** Could not find a valid destination directory for files in the %s section of the INF\n"), pszFileGroupName);
			AddLineToLogFile(g_pszErrorLogFilename, szLogString);
		}

		return FALSE;
	}

	return TRUE;
}


/******************************************************************
Function Name:  BuildDestinationDirectory
Author:  aaronste
Description: Parses an INF file destination directory line
			 and builds a full destination directory from it
Inputs:	pszDestDirLine - full line from an INF file destination
						 directory section
		pszInfFilename - full path to INF file being parsed
		iRegType - whether we want AddReg or DelReg
Results: TRUE if we are able to parse the file, FALSE otherwise
******************************************************************/
BOOL BuildDestinationDirectory(TCHAR *pszDestDirLine, TCHAR *pszFullDestDir)
{
	TCHAR *pszStartOfDestDir = NULL;
	TCHAR *pszEndOfDestDir = NULL;
	TCHAR *pszDirectoryToken = NULL;
	TCHAR *c;
	TCHAR szWinDir[MAX_PATH+1];
	TCHAR szSysDir[MAX_PATH+1];
	TCHAR szProgramDir[MAX_PATH+1];
	HKEY hk;
	DWORD dwRegType = REG_SZ;
	DWORD dwSize = MAX_PATH+1;
	TCHAR szLogString[MAX_PATH];

	// Check input parameter
	if (!pszDestDirLine)
		return FALSE;

	// ************************************************************************
	//  The line is of the format [File Group] = [Token #], [Rest of Dest Dir]
	//  The [Rest of Dest Dir] part of the line is optional
	// ************************************************************************
	
	// Retrieve the first part of the line
	pszStartOfDestDir = GetNextCommaDelimitedLineItem(&pszDestDirLine);
	if (!pszStartOfDestDir)
	{
		_stprintf_s(szLogString,MAX_PATH,_T("***ERROR*** Invalid directory token %s"),pszDestDirLine);
		AddLineToLogFile(g_pszErrorLogFilename, szLogString);
		return FALSE;
	}

	// If there is a 2nd part of the line, retrieve it also
	if (pszDestDirLine)
	{
		pszEndOfDestDir = GetNextCommaDelimitedLineItem(&pszDestDirLine);
	}

	// Find the substring of the first part of the line after the = sign
	if(c = _tcsrchr(pszStartOfDestDir, _T('=')))
	{
		*c = _T('\0');
		pszDirectoryToken = c+1;

		// Trim blank characters at the front of the token
		while (pszDirectoryToken[0] == _T(' '))
			pszDirectoryToken++;

		// Trim comments from the end of the token
		if(c = _tcsrchr(pszDirectoryToken, _T(';')))
	 		*c = _T('\0');

		// Trim blanks from the end of the token
		RemoveTrailingSpaces(pszDirectoryToken);
	}

	// If we didn't get a directory token value, bail out
	if (!pszDirectoryToken)
		return FALSE;

	// Get Windows directory
	if (GetWindowsDirectory(szWinDir, MAX_PATH+1) == 0)
		return FALSE;

	// Get system directory
	if (GetSystemDirectory(szSysDir, MAX_PATH+1) == 0)
		return FALSE;

	// Get Program Files directory
	if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\"), 0, KEY_READ | KEY_WRITE, &hk)!= ERROR_SUCCESS)
		szProgramDir[0] = _T('\0');
	if(RegQueryValueEx(hk, _T("ProgramFilesDir"), 0, &dwRegType, (LPBYTE)&szProgramDir, &dwSize)!= ERROR_SUCCESS)
		szProgramDir[0] = _T('\0');
	RegCloseKey(hk);

	// Substitute for the token value
	// We currently only understand the following values:
	// 10 = Windows directory
	// 25 = Windows directory
	// 11 = Windows system directory
	// 16422 = Program Files directory
	// 65619 = DLLCache - we want to explicitly skip this one
	// 49000 = DLLCache - we want to explicitly skip this one
	// because XP Embedded doesn't have SFP or a DLLCache
	if ( (0 == _tcscmp (pszDirectoryToken, _T("10"))) || (0 == _tcscmp (pszDirectoryToken, _T("25"))) )
		_tcscpy(pszFullDestDir, szWinDir);
	else if (0 == _tcscmp (pszDirectoryToken, _T("11")))
		_tcscpy(pszFullDestDir, szSysDir);
	else if (0 == _tcscmp (pszDirectoryToken, _T("16422")))
		_tcscpy(pszFullDestDir, szProgramDir);
	else if (0 == _tcscmp (pszDirectoryToken, _T("65619")))
		return TRUE;
	else if (0 == _tcscmp (pszDirectoryToken, _T("49000")))
		return TRUE;
	else
	{
		_stprintf_s(szLogString,MAX_PATH,_T("***ERROR*** Unrecognized directory token value %s - need to update Inf2Rtf to understand this value"),pszDirectoryToken);
		AddLineToLogFile(g_pszErrorLogFilename, szLogString);
		return FALSE;
	}

	// If it exists, concatenate the 2nd part of the destination directory
	if (pszEndOfDestDir)
	{
		_tcscat(pszFullDestDir, _T("\\"));
		_tcscat(pszFullDestDir, pszEndOfDestDir);
	}

	return TRUE;
}


/******************************************************************
Function Name:  ParseInfRegistrySection
Author:  aaronste
Description: Parses the list of section names under AddReg/DelReg
			 in the INF file, and tries to verify the files for
			 each of the sections
Inputs:	pszListOfRegistryGroups - list of groups of files in the INF
		pszInfFilename - full path to INF file being parsed
Results: TRUE if successful, FALSE otherwise
******************************************************************/
BOOL ParseInfRegistrySection(TCHAR *pszListOfRegistryGroups, TCHAR *pszInfFilename)
{
	TCHAR *pszRegGroupHeader = NULL;
	BOOL bRetValue = TRUE;

	// If the input parameters are invalid, bail out
	if ( (!pszListOfRegistryGroups) || (!pszInfFilename) )
		return FALSE;

	// The pszListOfRegistryGroups is a comma-delimited list of sections
	// Parse the string to get each section
	while (pszListOfRegistryGroups)
	{
		pszRegGroupHeader = GetNextCommaDelimitedLineItem(&pszListOfRegistryGroups);
		if (pszRegGroupHeader)
		{
			// Skip the uninstall registry section because we don't need to write
			// ARP registry entries in an XP Embedded scenario
			if (_tcsstr(pszRegGroupHeader, UNINSTALL_REG_SECTION))
				continue;

			if (!ParseInfRegistryGroup(pszRegGroupHeader, pszInfFilename))
				bRetValue = FALSE;
		}
	}

	return bRetValue;
}


/******************************************************************
Function Name:  ParseInfRegistryGroup
Author:  aaronste
Description: Parses the INF file, finds the desired registry section
			 and verifies that each value in the section is either
			 on the machine or not
Inputs:	pszSectionName - name of the section in the INF file
		pszInfFilename - full path to INF file being parsed
Results: TRUE if we are able to parse the file, FALSE otherwise
******************************************************************/
BOOL ParseInfRegistryGroup(TCHAR *pszSectionName, TCHAR *pszInfFilename)
{
	FILE *pfInfFile = NULL;
	TCHAR szBracketedSectionName[MAX_PATH];
	TCHAR szInfFileLine[LINE_LENGTH];
	TCHAR szTempBuf[LINE_LENGTH];

	// If the input parameters are invalid, bail out
	if ( (!pszSectionName) || (!pszInfFilename) )
		return FALSE;

	// Put brackets around the section name, it is passed in without them
	_tcscpy(szBracketedSectionName,_T("["));
	_tcscat(szBracketedSectionName,pszSectionName);
	_tcscat(szBracketedSectionName,_T("]"));

	// Open the INF file for reading
	pfInfFile = _tfopen(pszInfFilename, _T("rb"));
	if (!pfInfFile)
		return FALSE;

	// Find the section header in the INF
	while (GetNextLine(pfInfFile, szInfFileLine))
	{
		if (_tcsstr(_tcslwr(szInfFileLine),_tcslwr(szBracketedSectionName)))
			break;
	}

	// Now we're in the correct registry section, each line in the section is a key
	// that we need to verify
	// The end of the section will be marked by a new section header or EOF
	while (GetNextLine(pfInfFile, szInfFileLine) && (_tcschr(szInfFileLine,'[') != szInfFileLine) )
	{
		// Skip blank lines
		if (0 == _tcscmp(szInfFileLine,_T("")))
			continue;

		// CheckInfRegistryValue modifies the string, so save it to a temp buffer
		_tcscpy(szTempBuf, szInfFileLine);

		// Call the registry value parsing method
		ParseInfRegValue(szTempBuf, pszInfFilename);
	}
	
	// Close the file
	if (pfInfFile) fclose(pfInfFile);

	return TRUE;
}


/******************************************************************
Function Name:  ParseInfRegValue
Author:  aaronste
Description: Parses a registry entry line in INF file format and
			 determines if the value is in the registry or not
Inputs:	pszInfFileLine - a line from a registry section of an INF
		pszInfFilename - full path of INF file we are parsing
Results: TRUE if the value is in the registry, FALSE otherwise
******************************************************************/
BOOL ParseInfRegValue(TCHAR *pszInfFileLine, TCHAR *pszInfFilename)
{
	TCHAR *pszRegHive = NULL;
	TCHAR *pszRegKey = NULL;
	TCHAR *pszRegValueEntry = NULL;
	TCHAR *pszRegKeyFlags = NULL;
	TCHAR *pszRegValue = NULL;
	
	TCHAR *pszToken = NULL;
	TCHAR *pszCharAfterToken = NULL;

	TCHAR szLogString[MAX_PATH*3];
	TCHAR szRegKeyName[MAX_PATH];
	TCHAR szRegValue[MAX_PATH];
	TCHAR szRegDataType[MAX_PATH];
	TCHAR szRegDataTypeTemp[MAX_PATH];
	TCHAR *pszExpectedRegValueData = NULL;

	BOOL bRetValue = TRUE;

	int iTokenCount = 1;

	// Check input parameters
	if ( NULL == pszInfFileLine)
		return FALSE;

	// The line is of the following format:
	// [reg-root],[subkey],[value-entry-name],[flags],[value]
	// Only [reg-root] is guaranteed to be there, all others are optional
	// *******************************************************************
	//  NOTE: Can't use strtok to parse the string because the last value
	//		  could be a comma-delimited list itself
	// *******************************************************************
	pszRegHive = GetNextCommaDelimitedLineItem(&pszInfFileLine);
	
	if (!pszRegHive)
		return FALSE;

	// Try to retrieve the optional values for Reg Key name, Value name, and Key type
	// Must check the pszInfFileLine each time because if we get to 
	// the end of the string, GetNextCommaDelimitedLineItem makes it NULL
	if (pszInfFileLine)
	{
		pszRegKey = GetNextCommaDelimitedLineItem(&pszInfFileLine);

		// Substitute any tokens in the value name
		if (_tcschr(pszRegKey,_T('%')))
		{
			_tcscpy(szRegKeyName, SubstituteForTokens(pszRegKey, pszInfFilename));
		}
		else
		{
			_tcscpy(szRegKeyName, pszRegKey);
		}
	}

	if (pszInfFileLine)
		pszRegValueEntry = GetNextCommaDelimitedLineItem(&pszInfFileLine);

	if (pszInfFileLine)
		pszRegKeyFlags	 = GetNextCommaDelimitedLineItem(&pszInfFileLine);

	// Get the registry value name
	_tcscpy(szRegValue, _T("\0"));
	if (pszRegValueEntry)
	{
		// Substitute any tokens in the value name
		if (_tcschr(pszRegValueEntry,_T('%')))
		{
			_tcscpy(szRegValue, SubstituteForTokens(pszRegValueEntry, pszInfFilename));
		}
		else
		{
			_tcscpy(szRegValue, pszRegValueEntry);
		}
	}

	// Get the type of registry value
	if (pszRegKeyFlags)
	{
		if (0 == _tcscmp(pszRegKeyFlags,_T("")))
		{
			_tcscpy(szRegDataType, _T("REG_SZ"));
		}
		else
		{
			// Substitute any tokens in the value name
			if (_tcschr(pszRegKeyFlags,_T('%')))
			{
				_tcscpy(szRegDataTypeTemp, SubstituteForTokens(pszRegKeyFlags, pszInfFilename));
			}
			else
			{
				_tcscpy(szRegDataTypeTemp, pszRegKeyFlags);
			}

			// Set the default data type to REG_SZ
			_tcscpy(szRegDataType, _T("REG_SZ"));

			// Check to see if the data type is one of the other known values
			if ( (0 == _tcscmp(szRegDataTypeTemp,_T("0x10001"))) || (0 == _tcscmp(szRegDataTypeTemp,_T("0x00010001"))) ||
				 (0 == _tcscmp(szRegDataTypeTemp,_T("0x10003"))) || (0 == _tcscmp(szRegDataTypeTemp,_T("0x00010003"))) )
			{
				_tcscpy(szRegDataType, _T("REG_DWORD"));
			}
			if ( (0 == _tcscmp(szRegDataTypeTemp,_T("0x00001"))) || (0 == _tcscmp(szRegDataTypeTemp,_T("0x00000001"))) )
			{
				_tcscpy(szRegDataType, _T("REG_BINARY"));
			}
			if ( (0 == _tcscmp(szRegDataTypeTemp,_T("0x10000"))) || (0 == _tcscmp(szRegDataTypeTemp,_T("0x00010000"))) )
			{
				_tcscpy(szRegDataType, _T("REG_MULTI_SZ"));
			}
			if ( (0 == _tcscmp(szRegDataTypeTemp,_T("0x20000"))) || (0 == _tcscmp(szRegDataTypeTemp,_T("0x00020000"))) )
			{
				_tcscpy(szRegDataType, _T("REG_EXPAND_SZ"));
			}
			if ( (0 == _tcscmp(szRegDataTypeTemp,_T("0x10008"))) || (0 == _tcscmp(szRegDataTypeTemp,_T("0x00010008"))) )
			{
				_tcscpy(szRegDataType, _T("REG_ADDREG_APPEND"));
			}
		}
	}

	// Try to retrieve the optional value for Reg Value Data
	// Must check the pszInfFileLine each time because if we get to 
	// the end of the string, GetNextCommaDelimitedLineItem makes it NULL
	// ********************************************************************
	//  NOTE: Must do this after checking the Key Type, because the
	//  REG_BINARY values may not be quoted, so we cannot use the
	//  function GetNextCommaDelimitedLineItem to get the value
	// ********************************************************************
	if (pszInfFileLine)
	{
		if (0 == _tcscmp(szRegDataType, _T("REG_BINARY")))
			pszRegValue = pszInfFileLine;
		else
			pszRegValue	= GetNextCommaDelimitedLineItem(&pszInfFileLine);

		// Also remove trailing spaces from the registry value data here
		RemoveTrailingSpaces(pszRegValue);
	}

	// Get the registry value data to search for
	if (pszRegValue)
	{
		if (0 == _tcscmp(pszRegValue,_T("")))
		{
			pszExpectedRegValueData = new TCHAR[MAX_PATH*10];
			_tcscpy(pszExpectedRegValueData, _T("\0"));
		}
		else
		{
			// Substitute any tokens in the value data
			if (_tcschr(pszRegValue,_T('%')))
				pszExpectedRegValueData = SubstituteForTokens(pszRegValue, pszInfFilename);
			else
			{
				pszExpectedRegValueData = new TCHAR[MAX_PATH*10];
				_tcscpy(pszExpectedRegValueData,pszRegValue);
			}
		}

	}

	// Retrieve the DWORD value in decimal if there is a registry data value
	// and the data type is REG_DWORD
	if ( (NULL != pszExpectedRegValueData) && (0 == _tcscmp(szRegDataType, _T("REG_DWORD"))) )
	{
		int iPower = 10;
		int iDwordValue = 0;

		// Check to see if there is an x in the value data
		TCHAR *c;
		if (c = _tcsrchr(pszExpectedRegValueData, _T('x')))
		{
			// Indicate that this is a hex value and increment the pointer
			iPower = 16;
			pszExpectedRegValueData = c+1;
		}

		int iValueLength = (int)_tcslen(pszExpectedRegValueData) - 1;

		// If we get here we found an x, which means the value is in hex
		// So calculate the value as a base-10 integer
		for (int i = iValueLength; i >= 0; i--)
		{
			// Skip zeroes
			if ('0' == pszExpectedRegValueData[i])
				continue;

			// Add the power of the current value
			szLogString[0] = pszExpectedRegValueData[i];
			szLogString[1] = '\0';
			iDwordValue += (int)_tstoi(szLogString)*(int)pow((double)iPower, iValueLength - i);
		}

		// Copy the computed value back into the string for later use
		_itot(iDwordValue, pszExpectedRegValueData, 10);
	}

	// Write the registry information to the HTML file
	if (NULL != pszExpectedRegValueData)
        _stprintf_s(szLogString, MAX_PATH*3, _T("<tr style=\"font-size: 10pt;\">\n<td>%s\\%s</td>\n<td>%s</td>\n<td>%s</td>\n<td>%s</td>\n</tr>"), pszRegHive, szRegKeyName, szRegValue, szRegDataType, pszExpectedRegValueData);
	else
        _stprintf_s(szLogString, MAX_PATH*3, _T("<tr style=\"font-size: 10pt;\">\n<td>%s\\%s</td>\n<td>%s</td>\n<td></td>\n<td></td>\n</tr>"), pszRegHive, szRegKeyName, szRegValue);

	AddLineToLogFile(g_pszRtfFilename, szLogString);

	return bRetValue;
}


/******************************************************************
Function Name:  GetNextLine
Author:  ashishw
Description: Gets the Next Line from the File.  If EOF is encountered 
	   then the function returns a NULL String
Inputs:
Results:
******************************************************************/
BOOL GetNextLine(FILE *pfFile, TCHAR *pszBuffer)
{
	TCHAR *pcbuf;
	
	if(!pszBuffer)
		return FALSE;
	
	if ((_fgetts(pszBuffer, LINE_LENGTH, pfFile)) && ((_tcscmp(pszBuffer,_T(""))) != 0))
	{
		if ((_tcscmp(pszBuffer,_T("\r\n"))) == 0)
		{	
			_tcscpy(pszBuffer, _T(""));
			return TRUE;
		}
		else if ((_tcscmp(pszBuffer,_T("\n"))) == 0)
		{	
			_tcscpy(pszBuffer, _T(""));
			return TRUE;
		}
		else
		{
			pcbuf = _tcsstr(pszBuffer, _T("\r\n"));
			if (pcbuf != NULL)
				*pcbuf = _T('\0');

			pcbuf = _tcschr(pszBuffer,_T('\n')); 	
			if (pcbuf != NULL)
				*pcbuf = _T('\0');

			return TRUE;
		}
	}
	else // EOF Encountered.
	{
		*pszBuffer = _T('\0');
		return FALSE;
	}
	
}


/******************************************************************
Function Name:  GetNextCommaDelimitedLineItem
Author:  aaronste
Description: Gets the next comma-delimited item from an INF file
			 registry line.  If it is enclosed in double-quotes, this
			 method will strip the quotes off
Inputs:	pszInfRegistryLine - pointer to the line, where the beginning
			of the pointer is the beginning of the desired item.  If 
			the function returns the entire value of the line, it will
			set the input string to NULL
Results: pointer to the next value
NOTE: pszInfRegistryLine is modified by this function, the returned
	  value is stripped off of the front of the string
******************************************************************/
TCHAR* GetNextCommaDelimitedLineItem(TCHAR **pszInfRegistryLine)
{
	TCHAR *pszToken = NULL, *pszTokenClosedQuote = NULL;
	TCHAR *pszCharAfterToken = NULL, *pszCharAfterClosedQuote = NULL;
	TCHAR *pszReturnValue = NULL;

	// Check input value
	if (!(*pszInfRegistryLine))
		return pszReturnValue;

	// Trim blank characters at the front of the string
	while ((*pszInfRegistryLine)[0] == _T(' '))
		(*pszInfRegistryLine)++;

	// Check to see if the next value is surrounded by double-quotes
	if ((*pszInfRegistryLine)[0] == _T('"'))
	{
		// Try to find the open quotation mark
		pszToken = _tcsstr((*pszInfRegistryLine), _T("\""));

		// Try to find the closing quotation mark
		pszTokenClosedQuote = _tcsstr((*pszInfRegistryLine)+1, _T("\""));
		
		// If there is no closed quote, just return the entire string
		if (!pszTokenClosedQuote)
		{
			pszReturnValue = (*pszInfRegistryLine);
			*pszInfRegistryLine = NULL;
			return pszReturnValue;
		}

		// Cache the pointer locations so we can return the shortened string
		pszCharAfterToken = pszToken + 1;
		pszCharAfterClosedQuote = pszTokenClosedQuote + 1;

		// Set the return value
		pszReturnValue = pszCharAfterToken;

		// Null out the quotation marks
		*pszToken = NULL;
		*pszTokenClosedQuote = NULL;

		// Move the line pointer to point to the character after the token
		*pszInfRegistryLine = pszCharAfterClosedQuote;

		// We could still have a comma after the last quotation mark
		// Check for it and get rid of it
		pszToken = _tcsstr((*pszInfRegistryLine), _T(","));

		// If there are no commas, we are at the end of the line
		// so set the passed-in string to NULL
		if (!pszToken)
		{
			*pszInfRegistryLine = NULL;
			return pszReturnValue;
		}

		// If there is a comma, just remove it by incrementing the pointer
		// of the passed-in string
		pszCharAfterToken = pszToken + 1;
		*pszToken = NULL;
		*pszInfRegistryLine = pszCharAfterToken;

	}
	else
	{
		// Not surrounded by quotes, just search for next comma
		pszToken = _tcsstr((*pszInfRegistryLine), _T(","));
		
		// If there are no commas in the line, return the entire string
		if (!pszToken)
		{
			pszReturnValue = (*pszInfRegistryLine);
			*pszInfRegistryLine = NULL;
			return pszReturnValue;
		}

		// Cache the pointer locations so we can return the shortened string
		pszCharAfterToken = pszToken + 1;

		// Now simulate strtok by NULLing out the pointer
		*pszToken = NULL;
		pszReturnValue = (*pszInfRegistryLine);

		// Move the line pointer to point to the character after the token
		*pszInfRegistryLine = pszCharAfterToken;

	}

	return pszReturnValue;
}


/******************************************************************
Function Name: GetValue
Author: scottros
Description:	Get the value of a reg key or check for existence
Inputs:		HKEY hk:			The hk of the key to save
			TCHAR * pszKey:		Name of the key to retrieve
			TCHAR * pszValue:	The value that will be retrieved
			DWORD dwType		The type of the value that will be retrieved
			LPBYTE data			A buffer to save the retriced data
			DWORD dwSize		The size of the data retrieved

Results:	TRUE is all was successful
			FALSE if there was an error
******************************************************************/
BOOL GetValue(HKEY hk, TCHAR * pszKey, TCHAR * pszValue, DWORD dwType, LPBYTE data, DWORD dwSize, BOOL bUseRootHiveIf64Bit)
{
	HKEY hkOpened;
	BOOL bRetValue = TRUE;

	if(!hk)
		hk = HKEY_LOCAL_MACHINE;

	// Use GetValueEx (which checks for WOW64) if the flag is true
	if (bUseRootHiveIf64Bit)
		return GetValueEx(hk, pszKey, pszValue, dwType, data, dwSize);

	if(RegOpenKeyEx(hk, pszKey,	0, KEY_READ | KEY_WRITE, &hkOpened)!= ERROR_SUCCESS)
	{
		return FALSE;
	}

	// The user could pass NULL for type, data, and size in order to just determine
	// if a registry value exists (without caring about what its data is)
	if ( (dwType) && (data) && (dwSize) )
	{
		bRetValue = (RegQueryValueEx(hkOpened, pszValue, 0, &dwType, (LPBYTE)data, &dwSize ) == ERROR_SUCCESS);
	}
	else
	{
		bRetValue = (RegQueryValueEx(hkOpened, pszValue, 0, NULL, NULL, NULL ) == ERROR_SUCCESS);
	}

	RegCloseKey(hkOpened);
	return bRetValue;
}


/******************************************************************
Function Name: GetValueEx
Author: aaronste
Description:	Get the value of a reg key.  Checks to see if the current process
				is running in WOW64 layer, and if so, forces the key
				to be opened in the registry root instead of the WOW64
				subhive
Inputs:		HKEY hk:			The hk of the key to save
			TCHAR * pszKey:		Name of the key to retrieve
			TCHAR * pszValue:	The value that will be retrieved
			DWORD dwType		The type of the value that will be retrieved
			LPBYTE data			A buffer to save the retriced data
			DWORD dwSize		The size of the data retrieved

Results:	TRUE is all was successful
			FALSE if there was an error
******************************************************************/
BOOL GetValueEx(HKEY hk, TCHAR * pszKey, TCHAR * pszValue, DWORD dwType, LPBYTE data, DWORD dwSize)
{
	HKEY hkOpened;
	BOOL bIsWow64Running = FALSE;
	BOOL bRetValue = TRUE;

	if(!hk)
		hk = HKEY_LOCAL_MACHINE;

	// Check to see if current process is running in WOW64 layer
	bIsWow64Running = IsCurrentProcessWOW64();
	
	if (!bIsWow64Running)
	{
		// Open the key
		if(RegOpenKeyEx(hk, pszKey,	0, KEY_WRITE | KEY_READ , &hkOpened)!= ERROR_SUCCESS)
		{
			return FALSE;
		}
	}
	else
	{
		// Open the key (using the root instead of the WOW64 subhive)
		if(RegOpenKeyEx(hk, pszKey,	0, KEY_WRITE | KEY_READ | KEY_WOW64_64KEY, &hkOpened)!= ERROR_SUCCESS)
		{
			return FALSE;
		}
	}

	// The user could pass NULL for type, data, and size in order to just determine
	// if a registry value exists (without caring about what its data is)
	if ( (dwType) && (data) && (dwSize) )
	{
		bRetValue = (RegQueryValueEx(hkOpened, pszValue, 0, &dwType, (LPBYTE)data, &dwSize ) == ERROR_SUCCESS);
	}
	else
	{
		bRetValue = (RegQueryValueEx(hkOpened, pszValue, 0, NULL, NULL, NULL ) == ERROR_SUCCESS);
	}

	RegCloseKey(hkOpened);
	return bRetValue;
}


/******************************************************************
Function Name	: IsCurrentProcessWOW64
Author			: a-jegibs
Description		: Checks to see if the current process is running
				  in the WOW64 layer
Inputs			: None
Results			: TRUE if the process is running in WOW64
				  FALSE otherwise
******************************************************************/
BOOL IsCurrentProcessWOW64()
{
	typedef BOOL (WINAPI *PFnIsWow64Process) (HANDLE , PBOOL);
	PFnIsWow64Process pfnIsWow64Process;
	HANDLE hCurHandle;
	HMODULE hmodK32 = NULL;
	BOOL bIsWow64Running = FALSE;

	// Delay load kernel32.dll because IsWow64Process only exists on
	// WinXP and higher OS types
	hmodK32 = GetModuleHandle(_T("kernel32.dll"));
	if (hmodK32) 
	{
		pfnIsWow64Process = (PFnIsWow64Process) GetProcAddress(hmodK32, "IsWow64Process");
		if (pfnIsWow64Process) 
		{
			// Get handle to current process
			hCurHandle=GetCurrentProcess();

			// Check to see if current process is running in WOW64 layer
			pfnIsWow64Process(hCurHandle , &bIsWow64Running);
		}
	}

	return bIsWow64Running;
}


/******************************************************************
Function Name:  MergeToMasterLogFile
Author:  aaronste
Description: Copies the contents of one file into another file
Inputs: pszDestFile - name of file to copy to
		pszFileToCopy - name of file to copy from
Results: TRUE if successful; FALSE otherwise
******************************************************************/
BOOL MergeToMasterLogFile(TCHAR *pszDestFile, TCHAR *pszFileToCopy)
{
	FILE *pfDestFile = NULL, *pfFileToCopy = NULL;
	TCHAR szTempBuf[LINE_LENGTH];

	// Check input parameters
	if ( (!pszDestFile) || (!pszFileToCopy) )
		return FALSE;

	// Open the destination file for append
	pfDestFile = _tfopen(pszDestFile, _T("a"));
	if (!pfDestFile)
		return FALSE;

	// Open the file to copy for read
	pfFileToCopy = _tfopen(pszFileToCopy, _T("r"));
	if (!pfFileToCopy)
		return FALSE;

	// Now append each line from the file to copy
	while (GetNextLine(pfFileToCopy, szTempBuf))
		_ftprintf(pfDestFile,_T("%s\n"),szTempBuf);

	// Close the files
	if (pfDestFile)		fclose(pfDestFile);
	if (pfFileToCopy)	fclose(pfFileToCopy);

	return TRUE;
}


/******************************************************************
Function Name:  ParseDefaultInstallINF
Author:  aaronste
Description: Parses an INF file with a "default install" section
			 and examines sections for copying files, adding registry
			 keys, and deleting registry keys
Inputs:	 pszTmpInfFile - full path to the INF file being parsed
		 pszSectionName - name of the "default install" section
Results: TRUE if successful, FALSE otherwise
******************************************************************/
BOOL ParseDefaultInstallINF(TCHAR *pszTmpInfFile, TCHAR *pszSectionName)
{
	TCHAR szCopyFilesString[LINE_LENGTH];
	TCHAR szAddRegString[MAX_PATH*3];
	TCHAR szDelRegString[MAX_PATH*3];
	TCHAR szKBArticleNumber[MAX_PATH];
	TCHAR szKBArticleNumberNoQ[MAX_PATH];
	TCHAR *c = NULL;
	BOOL bRetValue = TRUE;

	// Check input parameters
	if ( (NULL == pszTmpInfFile) || (NULL == pszSectionName) )
		return FALSE;

	// Get the section names for CopyFiles, AddReg, DelReg
	GetPrivateProfileString(pszSectionName, COPYFILES, _T("0"), szCopyFilesString, LINE_LENGTH, pszTmpInfFile);
	GetPrivateProfileString(pszSectionName, ADD_REG, _T("0"), szAddRegString, MAX_PATH*3, pszTmpInfFile);
	GetPrivateProfileString(pszSectionName, DEL_REG, _T("0"), szDelRegString, MAX_PATH*3, pszTmpInfFile);

	// Retrieve the KB article number from the INF
	GetPrivateProfileString(STRINGS_HEADER, _T("Compid"), _T("0"), szKBArticleNumber, MAX_PATH, pszTmpInfFile);
	if (0 == _tcscmp(szKBArticleNumber, _T("0")))
	{
		AddLineToLogFile(g_pszErrorLogFilename, _T("***ERROR*** Could not find KB article number in INF file - there is no Compid value in the [Strings] section"));
		_tcscpy(szKBArticleNumber, _T("<QNUMBER>"));
	}
	else
	{
		// Get the QFE number without a Q on the beginning
		c = _tcsstr(szKBArticleNumber, _T("Q"));
		if (NULL != c)
			c = c + 1;
		else
			c = szKBArticleNumber;

		_stprintf_s(szKBArticleNumberNoQ, MAX_PATH, _T("%s"), c);
	}

	// Set the name of the output RTF file using the KB
	// article number unless an output file name was passed in
	if (NULL == g_pszRtfFilename)
	{
		TCHAR szTemp[MAX_PATH];
		_tcscpy(szTemp, _T("Additional_Info_"));
		_tcscat(szTemp, szKBArticleNumber);
		_tcscat(szTemp, _T(".HTM"));
		g_pszRtfFilename = AddTempPathToFileName(szTemp);
	}

	// Delete the output file if it already existed
	DeleteFile(g_pszRtfFilename);

	// Add the header to the additional info file
	AddHtmlHeaderToOutputFile(szKBArticleNumber);

	// Check the copyfiles section of the INF if the section value was found
	if (0 != _tcscmp(szCopyFilesString,_T("0")))
	{
		// Parse the files in the INF
		AddLineToLogFile(g_pszRtfFilename, FILE_ADD_HEADER);
		AddLineToLogFile(g_pszRtfFilename, GENERIC_TABLE_HEADER);
		AddLineToLogFile(g_pszRtfFilename, FILE_TABLE_HEADER);

		if (!ParseInfCopyFilesSection(szCopyFilesString, pszTmpInfFile, szKBArticleNumberNoQ))
			bRetValue = FALSE;
		
		AddLineToLogFile(g_pszRtfFilename, GENERIC_TABLE_FOOTER);
	}

	// Check the addreg section of the INF if the section value was found
	if (0 != _tcscmp(szAddRegString,_T("0")))
	{
		AddLineToLogFile(g_pszRtfFilename, REG_ADD_HEADER);
		AddLineToLogFile(g_pszRtfFilename, GENERIC_TABLE_HEADER);
		AddLineToLogFile(g_pszRtfFilename, REG_TABLE_HEADER);

		if (!ParseInfRegistrySection(szAddRegString, pszTmpInfFile))
			bRetValue = FALSE;

		AddLineToLogFile(g_pszRtfFilename, GENERIC_TABLE_FOOTER);
	}

	// Check the delreg section of the INF if the section value was found
	if (0 != _tcscmp(szDelRegString,_T("0")))
	{
		AddLineToLogFile(g_pszRtfFilename, REG_REMOVE_HEADER);
		AddLineToLogFile(g_pszRtfFilename, GENERIC_TABLE_HEADER);
		AddLineToLogFile(g_pszRtfFilename, REG_TABLE_HEADER);

		if (!ParseInfRegistrySection(szDelRegString, pszTmpInfFile))
			bRetValue = FALSE;

		AddLineToLogFile(g_pszRtfFilename, GENERIC_TABLE_FOOTER);
	}

	AddLineToLogFile(g_pszRtfFilename, HTML_FOOTER);

	return bRetValue;
}


/******************************************************************
Function Name:  ParseINF
Author:  aaronste
Description: Parses an INF file and determines what type of INF
			 it is, then calls a specialized parsing routine
			 based on the type
Inputs:	 pszTmpInfFile - full path to the INF file being parsed
Results: TRUE if successful, FALSE otherwise
******************************************************************/
BOOL ParseINF(TCHAR *pszTmpInfFile)
{
	TCHAR szLogString[MAX_PATH*3];
	TCHAR szBuffer[MAX_PATH];
	DWORD dwSectionData = 0;
	BOOL bRetValue = TRUE;

	// Search for known header sections to figure out which subroutine to
	// call to parse this INF
	if (GetPrivateProfileSection(DEFAULT_INSTALL_HEADER, szBuffer, MAX_PATH, pszTmpInfFile) > 0)
	{
		bRetValue = ParseDefaultInstallINF(pszTmpInfFile, DEFAULT_INSTALL_HEADER);
	}
	else if (GetPrivateProfileSection(CONFIGURATION_HEADER, szBuffer, MAX_PATH, pszTmpInfFile) > 0)
	{
		bRetValue = ParseWinSEINF(pszTmpInfFile);
	}
	else
	{
		// This is the fall-through case - log an error and exit
		_stprintf_s(szLogString,MAX_PATH*3,_T("***ERROR*** Unrecognized INF file format - Inf2Rtf cannot correctly parse %s\n"),pszTmpInfFile);
		AddLineToLogFile(g_pszErrorLogFilename, szLogString);
		bRetValue = FALSE;
	}

	return bRetValue;
}


/******************************************************************
Function Name:  ParseWinSEINF
Author:  aaronste
Description: Parses a MUI netfx INF file and examines section
			 for copying files
Inputs:	 pszTmpInfFile - full path to the INF file being parsed
Results: TRUE if successful, FALSE otherwise
******************************************************************/
BOOL ParseWinSEINF(TCHAR *pszTmpInfFile)
{
	BOOL bRetValue = TRUE;
	TCHAR szInfFileLine[LINE_LENGTH];
	TCHAR szKBArticleNumber[MAX_PATH];
	TCHAR szKBArticleNumberNoQ[MAX_PATH];
	TCHAR szAddRegString[MAX_PATH*3];
	TCHAR szDelRegString[MAX_PATH*3];
	TCHAR szFileSections[MAX_PATH*3] = {0};
	TCHAR *c = NULL;
	FILE *pfInfFile = NULL;

	// Check input parameter
	if (NULL == pszTmpInfFile)
		return FALSE;

	// Retrieve the KB article number from the INF
	GetPrivateProfileString(STRINGS_HEADER, _T("SP_SHORT_TITLE"), _T("0"), szKBArticleNumber, MAX_PATH, pszTmpInfFile);
	if (0 == _tcscmp(szKBArticleNumber, _T("0")))
	{
		AddLineToLogFile(g_pszErrorLogFilename, _T("***ERROR*** Could not find KB article number in INF file - there is no SP_SHORT_TITLE value in the [Strings] section"));
		_tcscpy(szKBArticleNumber, _T("<QNUMBER>"));
	}
	else
	{
		// WinSE INF files store the article number as KB######
		// We want to convert it to Q######
		c = _tcsstr(szKBArticleNumber, _T("KB"));
		if (NULL != c)
			c = c + 2;
		else
			c = szKBArticleNumber;

		_stprintf_s(szKBArticleNumberNoQ, MAX_PATH, _T("%s"), c);
		_stprintf_s(szKBArticleNumber, MAX_PATH, _T("Q%s"), szKBArticleNumberNoQ);
	}

	// Set the name of the output RTF file using the KB
	// article number unless an output file name was passed in
	if (NULL == g_pszRtfFilename)
	{
		TCHAR szTemp[MAX_PATH];
		_tcscpy(szTemp, _T("Additional_Info_"));
		_tcscat(szTemp, szKBArticleNumber);
		_tcscat(szTemp, _T(".HTM"));
		g_pszRtfFilename = AddTempPathToFileName(szTemp);
	}

	// Delete the output file if it already existed
	DeleteFile(g_pszRtfFilename);

	// Add the header to the additional info file
	AddHtmlHeaderToOutputFile(szKBArticleNumber);

	// Open the INF file for reading
	pfInfFile = _tfopen(pszTmpInfFile, _T("rb"));
	if (!pfInfFile)
		return FALSE;

	// Loop through all lines in the INF
	while (GetNextLine(pfInfFile, szInfFileLine))
	{
SECTION_COMPARE:
		if ( (_tcsstr(szInfFileLine, WINSE_ADDFILES_1)) || (_tcsstr(szInfFileLine, WINSE_ADDFILES_2)) || (_tcsstr(szInfFileLine, WINSE_ADDFILES_3)) )
		{
			// If we get here, we found a file copy section, so we need to parse
			// and find any subsections
			// NOTE - cannot use GetPrivateProfile API here because this type of
			// INF can have multiple CopyFiles sections
			while (GetNextLine(pfInfFile, szInfFileLine))
			{
				// If we have a new section header, jump back to to the section header comparison
				if (_tcschr(szInfFileLine,'[') == szInfFileLine)
					goto SECTION_COMPARE;

				// Skip blank lines
				if (0 == _tcscmp(szInfFileLine,_T("")))
					continue;

				// Check to see if the current line is a CopyFiles section
				if (_tcsstr(szInfFileLine, COPYFILES))
				{
					// If we get here, we have a CopyFiles section, so we need to 
					// parse off the part after the = sign
					c = _tcschr(szInfFileLine, '=');
					if (NULL != c)
					{
						// Increment the pointer to skip the = sign
						c++;

						// Add the copyfiles section to the tracking variable for later
						if (0 != _tcslen(szFileSections))
						{
							_tcscat(szFileSections, _T(","));
							_tcscat(szFileSections, c);
						}
						else
						{
							_tcscpy(szFileSections, c);
						}
					}
				}

			}
		}
	}
	
	// Close the INF file
	if (pfInfFile) fclose(pfInfFile);

	// If we found any CopyFiles sections, parse them now
	if (0 != _tcslen(szFileSections))
	{
		AddLineToLogFile(g_pszRtfFilename, FILE_ADD_HEADER);
		AddLineToLogFile(g_pszRtfFilename, GENERIC_TABLE_HEADER);
		AddLineToLogFile(g_pszRtfFilename, FILE_TABLE_HEADER);

		if (!ParseInfCopyFilesSection(szFileSections, pszTmpInfFile, szKBArticleNumberNoQ))
			bRetValue = FALSE;

		AddLineToLogFile(g_pszRtfFilename, GENERIC_TABLE_FOOTER);
	}

	// Try to retrieve the section names for AddReg and DelReg
	GetPrivateProfileString(WINSE_REGISTRY_ADD, ADD_REG, _T("0"), szAddRegString, MAX_PATH*3, pszTmpInfFile);
	GetPrivateProfileString(WINSE_REGISTRY_ADD, DEL_REG, _T("0"), szDelRegString, MAX_PATH*3, pszTmpInfFile);

	// Check the addreg section of the INF if the section value was found
	if (0 != _tcscmp(szAddRegString,_T("0")))
	{
		AddLineToLogFile(g_pszRtfFilename, REG_ADD_HEADER);
		AddLineToLogFile(g_pszRtfFilename, GENERIC_TABLE_HEADER);
		AddLineToLogFile(g_pszRtfFilename, REG_TABLE_HEADER);

		if (!ParseInfRegistrySection(szAddRegString, pszTmpInfFile))
			bRetValue = FALSE;

		AddLineToLogFile(g_pszRtfFilename, GENERIC_TABLE_FOOTER);
	}

	// Check the delreg section of the INF if the section value was found
	if (0 != _tcscmp(szDelRegString,_T("0")))
	{
		AddLineToLogFile(g_pszRtfFilename, REG_REMOVE_HEADER);
		AddLineToLogFile(g_pszRtfFilename, GENERIC_TABLE_HEADER);
		AddLineToLogFile(g_pszRtfFilename, REG_TABLE_HEADER);

		if (!ParseInfRegistrySection(szDelRegString, pszTmpInfFile))
			bRetValue = FALSE;

		AddLineToLogFile(g_pszRtfFilename, GENERIC_TABLE_FOOTER);
	}

	AddLineToLogFile(g_pszRtfFilename, HTML_FOOTER);

	return bRetValue;
}


/******************************************************************
Function Name:	RemoveTrailingSpaces
Author:			aaronste
Description:	Removes any trailing spaces at the end of a string
Inputs:			TCHAR *pszString: String to remove spaces from
Results:		TRUE is all was successful
				FALSE if there was an error
******************************************************************/
BOOL RemoveTrailingSpaces(TCHAR *pszString)
{
	int i, iStrLen;

	if (NULL == pszString)
		return FALSE;

	iStrLen = (int)_tcslen(pszString);
	for (i=iStrLen-1; i>=0; i--)
	{
		if (_T(' ') == pszString[i])
			pszString[i] = _T('\0');
		else
			break;
	}

	return TRUE;
}


/******************************************************************
Function Name:  SearchFileForItem
Author:  aaronste
Description: Searches for a given string in a file
Inputs: pszFilename - name of file to parse
		pszSearchString - string to search for
Results: TRUE if the string is found; FALSE otherwise
******************************************************************/
BOOL SearchFileForItem(TCHAR *pszFilename, TCHAR *pszSearchString)
{
	FILE *pfFile;
	TCHAR szFileLine[LINE_LENGTH];

	// If the input parameters are invalid, bail out
	if ( (!pszFilename) || (!pszSearchString) )
		return FALSE;

	// Open the file for reading
	pfFile = _tfopen(pszFilename, _T("r"));
	if (!pfFile)
		return FALSE;

	// Try to find the string in the file
	while (GetNextLine(pfFile, szFileLine))
	{
		if (_tcsstr(_tcslwr(szFileLine),_tcslwr(pszSearchString)))
		{
			// If found, close the file and return TRUE
			if (pfFile) fclose(pfFile);
			return TRUE;
		}
	}

	// Close the file
	if (pfFile) fclose(pfFile);

	// If we got here the string was not found, return FALSE
	return FALSE;
}


/******************************************************************
Function Name:  ShowUsageInfo
Author:  aaronste
Description: Shows a message box with command line parameters
Inputs:	NONE
Results: NONE
******************************************************************/
void ShowUsageInfo()
{
	MessageBox(NULL, _T("Command Line Parameters:\n\n/i [INF name]\tFull path to an INF file\n/o [filename]\tFull path to an output file\n/e [filename]\tFull path to an error log file\n/q\t\tRun in quiet mode"), 
			   _T("inf2rtf Usage"), MB_OK|MB_ICONINFORMATION);

}


/******************************************************************
Function Name:  SubstituteForTokensOld
Author:  aaronste
Description: Substitutes for tokens in a given string and returns
			 a new string with the actual values
Inputs:	pszInString - string with tokens
Results: TCHAR - pointer to a new string with no tokens
******************************************************************/
TCHAR* SubstituteForTokensOld(TCHAR *pszInString)
{
	TCHAR *pszNewString = NULL;
	TCHAR *pcToken = NULL;
	TCHAR szWinDir[MAX_PATH];
	TCHAR szSysDir[MAX_PATH];

	// Check input value
	if (!pszInString)
		return NULL;

	// Get Windows directory
	if (GetWindowsDirectory(szWinDir, MAX_PATH) == 0)
		return NULL;

	// Get system directory
	if (GetSystemDirectory(szSysDir, MAX_PATH) == 0)
		return NULL;

	// Search for tokens that we understand and replace them
	// %10% = Windows directory
	// %11% = Windows system directory

	// Substitute the Windows directory token
	pcToken = _tcsstr(pszInString,_T("%10%"));
	if (pcToken != NULL)									 
	{
		// Increment the pointer
		pcToken = pcToken + 4;

		// If the token is not at the start of the string, terminate the
		// first part of what will become the new string
		*(pcToken-4) = NULL;

		// Build the new string
		pszNewString = new TCHAR[MAX_PATH];
		if (pszInString)
		{
			_tcscpy(pszNewString,pszInString);
			_tcscat(pszNewString,szWinDir);
			_tcscat(pszNewString,pcToken);
		}
		else
		{
			_tcscpy(pszNewString,szWinDir);
			_tcscat(pszNewString,pcToken);
		}

		// Allow for more than one token in a line
		return SubstituteForTokensOld(pszNewString);
	}

	// Substitute the system directory token
	pcToken = _tcsstr(pszInString,_T("%11%"));
	if (pcToken != NULL)									 
	{
		// Increment the pointer
		pcToken = pcToken + 4;

		// If the token is not at the start of the string, terminate the
		// first part of what will become the new string
		*(pcToken-4) = NULL;

		// Build the new string
		pszNewString = new TCHAR[MAX_PATH];
		if (pszInString)
		{
			_tcscpy(pszNewString,pszInString);
			_tcscat(pszNewString,szSysDir);
			_tcscat(pszNewString,pcToken);
		}
		else
		{
			_tcscpy(pszNewString,szSysDir);
			_tcscat(pszNewString,pcToken);
		}

		// Allow for more than one token in a line
		return SubstituteForTokensOld(pszNewString);
	}

	// If we get here there were no tokens, so just copy
	// the old string to the new string and return it
	pszNewString = new TCHAR[MAX_PATH];
	_tcscpy(pszNewString,pszInString);
	return pszNewString;
}


/******************************************************************
Function Name:  SubstituteForTokens
Author:  aaronste
Description: Substitutes for tokens in a given string and returns
			 a new string with the actual values
Inputs:	pszInString - string with tokens
Results: TCHAR - pointer to a new string with no tokens
******************************************************************/
TCHAR* SubstituteForTokens(TCHAR *pszInString, TCHAR *pszInfFilename)
{
	TCHAR *pszNewString = NULL;
	TCHAR *pcPercentOne = NULL;
	TCHAR *pcPercentTwo = NULL;
	TCHAR szEndOfString[MAX_PATH*10] = {0};
	TCHAR szTokenToReplace[MAX_PATH] = {0};
	TCHAR szStringValue[MAX_PATH] = {0};

	// Check input value
	if (NULL == pszInString)
		return NULL;

	// Search for a token by scanning for the first 2 % characters
	// in the passed-in string
	pcPercentOne = _tcschr(pszInString,_T('%'));
	if (NULL != pcPercentOne)									 
	{
		pcPercentTwo = _tcschr(pcPercentOne+1,_T('%'));
		if (NULL != pcPercentTwo)
		{
			// If we get here we have 2 % signs, so get the
			// string value between them
			_tcsncpy(szTokenToReplace, pcPercentOne+1, _tcslen(pcPercentOne) - _tcslen(pcPercentTwo) - 1);

			// Now search for this token in the Strings section
			// of the passed-in INF
			GetPrivateProfileString(STRINGS_HEADER, szTokenToReplace, _T("0"), szStringValue, MAX_PATH, pszInfFilename);

			if (0 != _tcscmp(szStringValue, _T("0")))
			{
				// Create the new string
				pszNewString = new TCHAR[MAX_PATH];

				// Rebuild the string with the substituted token
				if (pszInString == pcPercentOne)
				{
					_tcscpy(pszNewString, szStringValue);
				}
				else
				{
					_tcsncpy(pszNewString, pszInString, _tcslen(pszInString) - _tcslen(pcPercentOne));
					pszNewString[_tcslen(pszInString) - _tcslen(pcPercentOne)] = '\0';
					_tcscat(pszNewString, szStringValue);
				}

				// Recursively call the function to allow
				// for more than one token in a line
				_tcscpy(szEndOfString, SubstituteForTokens(pcPercentTwo+1, pszInfFilename));
				_tcscat(pszNewString, szEndOfString);
			}
			else
			{
				// Create the new string
				pszNewString = new TCHAR[MAX_PATH];

				// Rebuild the string
				_tcsncpy(pszNewString, pszInString, _tcslen(pszInString) - _tcslen(pcPercentTwo) + 1);
				pszNewString[_tcslen(pszInString) - _tcslen(pcPercentTwo) + 1] = '\0';

				// Recursively call the function to allow
				// for more than one token in a line
				_tcscpy(szEndOfString, SubstituteForTokens(pcPercentTwo+1, pszInfFilename));
				_tcscat(pszNewString, szEndOfString);
			}
		}
	}
	else
	{
		// If we get here there were no tokens, so just copy
		// the old string to the new string and return it
		pszNewString = new TCHAR[MAX_PATH];
		_tcscpy(pszNewString,pszInString);
	}

	return pszNewString;
}


/******************************************************************
Function Name:  ConvertINFToUnicode
Author:  aaronste
Description: Takes a full path to an INF file and checks to see
			 if it is in ANSI or UNICODE format.  If it is ANSI
			 it does nothing.  If it is UNICODE, it converts it
			 to ANSI and overwrites the file at the passed-in
			 location
Inputs:	pszFilename - full path to file to check format of
Results: TRUE if successful, FALSE if there was an error
******************************************************************/
BOOL ConvertINFToUnicode(TCHAR *pszFilename)
{

	HANDLE  hFile;
	FILE*	pfInfFile;
	DWORD   dwSize;
	DWORD   dwRead;
	BYTE*   pRawFileText;
	TCHAR*  pFileUnicode;

	// Try to get a handle to the filename passed in
	hFile = CreateFile(pszFilename,					// open file passed in
                       GENERIC_READ|GENERIC_WRITE,	// open for reading 
                       FILE_SHARE_READ,				// share for reading 
                       NULL,						// no security 
                       OPEN_EXISTING,				// existing file only 
                       FILE_ATTRIBUTE_NORMAL,		// normal file 
                       NULL);						// no attr. template 

	// Check to see if we have a valid file handle
	if(hFile == INVALID_HANDLE_VALUE) 
	{
		CloseHandle(hFile);
		return FALSE;
	} 
	else
	{
		dwSize = GetFileSize(hFile, NULL);
		pRawFileText = new BYTE[ dwSize + 2];
		memset(pRawFileText, 0, dwSize + 2);

		ReadFile(hFile, (LPVOID)pRawFileText, dwSize, &dwRead, NULL);

		if ( *((unsigned short*)pRawFileText) == 0xFEFF)
		{
			// If we get here, it is a UNICODE file, so return TRUE
			if (pRawFileText) delete [] pRawFileText;
			pRawFileText = NULL;
			CloseHandle(hFile);
			return TRUE;
		}
		else
		{
            // If we get here, it is an ANSI file, so convert the
			// buffer to a UNICODE string and then rewrite the file
			pFileUnicode = (TCHAR*)AllocWszFromSz((LPCSTR)pRawFileText);

			CloseHandle(hFile);

			// Open the passed in file for writing, we can overwrite existing info
			pfInfFile = _tfopen(pszFilename, _T("wb"));
			if (!pfInfFile)
			{
				if (pRawFileText) delete [] pRawFileText;
				pRawFileText = NULL;
				if (pFileUnicode) delete [] pFileUnicode;
				pFileUnicode = NULL;
				return FALSE;
			}

			// Write the ANSI contents to the file
			_ftprintf(pfInfFile, _T("%s"), pFileUnicode);

			// Clean up
			if (pfInfFile)
				fclose(pfInfFile);

			if (pRawFileText) delete [] pRawFileText;
			pRawFileText = NULL;
			if (pFileUnicode) delete [] pFileUnicode;
			pFileUnicode = NULL;

			return TRUE;
		}
	}

	// If we get here something went wrong, so return FALSE
	return FALSE;
}


/******************************************************************
Function Name:  AllocSzFromWsz
Author:  jeremyro
Description: Convert input Unicode (WCHAR) string to Ansi (CHAR).
			 The returned string is a newly allocated buffer
			 (the orginal buffer is untouched)
Inputs:	cwszBuffer - Unicode string that will be converted
Results: NULL if 'cwszBuffer' is NULL, or if an error occurs
		 Pointer to Ansi copy of 'cwszBuffer' otherwise
Notes: returned string was created with new [], it is caller's
	   responsibility to clean up the buffer with delete []
******************************************************************/
LPSTR AllocSzFromWsz(LPCWSTR cwszBuffer)
{
    LPSTR szOut = NULL;

    if(NULL != cwszBuffer)
    {
        int nRet = WideCharToMultiByte(CP_ACP, 0, cwszBuffer, -1, NULL, 0, NULL, NULL);

        if(0 < nRet)
        {
            ++nRet;
            szOut = new CHAR[nRet];
            if(szOut)
            {
                nRet = WideCharToMultiByte(CP_ACP, 0, cwszBuffer, -1, szOut, nRet, NULL, NULL);
            }
        }

        if(nRet)
        {
            DWORD dwErr = GetLastError();
        }
    }

    return szOut;
}


/******************************************************************
Function Name:  AllocWszFromSz
Author:  jeremyro
Description: Convert input Ansi (TCHAR) string to Unicode (WCHAR).
			 The returned string is a newly allocated buffer
			 (the orginal buffer is untouched)
Inputs:	cwszBuffer - Ansi string that will be converted
Results: NULL if 'cwszBuffer' is NULL, or if an error occurs
		 Pointer to Unicode copy of 'cwszBuffer' otherwise
Notes: returned string was created with new [], it is caller's
	   responsibility to clean up the buffer with delete []
******************************************************************/
LPWSTR AllocWszFromSz( LPCSTR cszBuffer )
{
    LPWSTR wszOut = NULL;


    if( NULL != cszBuffer )
    {
        int nRet = MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED | MB_ERR_INVALID_CHARS, cszBuffer, -1, NULL, 0 );

        if( 0 < nRet )
        {
            ++nRet;
            wszOut = new WCHAR[nRet];
            if( wszOut )
            {
                nRet = MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED | MB_ERR_INVALID_CHARS, cszBuffer, -1, wszOut, nRet );
            }
        }

        if( nRet )
        {
            DWORD dwErr = GetLastError();
        }
    }

    return wszOut;
}
